import os
import json
import datetime
import uuid
import astrbot.api.star as star
from astrbot.api.event import filter
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from astrbot.api.event import AstrMessageEvent, MessageEventResult
from astrbot.api import llm_tool, logger


@star.register(
    name="astrbot-reminder", desc="使用 LLM 待办提醒", author="Soulter", version="0.0.1"
)
class Main(star.Star):
    """使用 LLM 待办提醒。只需对 LLM 说想要提醒的事情和时间即可。比如：`之后每天这个时候都提醒我做多邻国`"""

    def __init__(self, context: star.Context) -> None:
        self.context = context
        self.scheduler = AsyncIOScheduler(timezone="Asia/Shanghai")

        # set and load config
        if not os.path.exists("data/astrbot-reminder.json"):
            with open("data/astrbot-reminder.json", "w", encoding="utf-8") as f:
                f.write("{}")
        with open("data/astrbot-reminder.json", "r", encoding="utf-8") as f:
            self.reminder_data = json.load(f)

        self._init_scheduler()
        self.scheduler.start()

    def _init_scheduler(self):
        """Initialize the scheduler."""
        for group in self.reminder_data:
            for reminder in self.reminder_data[group]:
                if "id" not in reminder:
                    id_ = str(uuid.uuid4())
                    reminder["id"] = id_
                else:
                    id_ = reminder["id"]

                if "datetime" in reminder:
                    if self.check_is_outdated(reminder):
                        continue
                    self.scheduler.add_job(
                        self._reminder_callback,
                        id=id_,
                        trigger="date",
                        args=[group, reminder],
                        run_date=datetime.datetime.strptime(
                            reminder["datetime"], "%Y-%m-%d %H:%M"
                        ),
                        misfire_grace_time=60,
                    )
                elif "cron" in reminder:
                    self.scheduler.add_job(
                        self._reminder_callback,
                        trigger="cron",
                        id=id_,
                        args=[group, reminder],
                        misfire_grace_time=60,
                        **self._parse_cron_expr(reminder["cron"]),
                    )

    def check_is_outdated(self, reminder: dict):
        """Check if the reminder is outdated."""
        if "datetime" in reminder:
            return (
                datetime.datetime.strptime(reminder["datetime"], "%Y-%m-%d %H:%M")
                < datetime.datetime.now()
            )
        return False

    async def _save_data(self):
        """Save the reminder data."""
        with open("data/astrbot-reminder.json", "w", encoding="utf-8") as f:
            json.dump(self.reminder_data, f, ensure_ascii=False)

    def _parse_cron_expr(self, cron_expr: str):
        fields = cron_expr.split(" ")
        return {
            "minute": fields[0],
            "hour": fields[1],
            "day": fields[2],
            "month": fields[3],
            "day_of_week": fields[4],
        }

    @llm_tool("reminder")
    async def reminder_tool(
        self,
        event: AstrMessageEvent,
        text: str = None,
        datetime_str: str = None,
        cron_expression: str = None,
        human_readable_cron: str = None,
    ):
        """Call this function when user is asking for setting a reminder.

        Args:
            text(string): Must Required. The content of the reminder.
            datetime_str(string): Required when user's reminder is a single reminder. The datetime string of the reminder, Must format with %Y-%m-%d %H:%M
            cron_expression(string): Required when user's reminder is a repeated reminder. The cron expression of the reminder.
            human_readable_cron(string): Optional. The human readable cron expression of the reminder.
        """
        if event.get_platform_name() == "qq_official":
            yield event.plain_result("reminder 暂不支持 QQ 官方机器人。")
            return

        if event.unified_msg_origin not in self.reminder_data:
            self.reminder_data[event.unified_msg_origin] = []

        if not cron_expression and not datetime_str:
            raise ValueError(
                "The cron_expression and datetime_str cannot be both None."
            )
        reminder_time = ""

        if not text:
            text = "未命名待办事项"

        if cron_expression:
            d = {
                "text": text,
                "cron": cron_expression,
                "cron_h": human_readable_cron,
                "id": str(uuid.uuid4()),
            }
            self.reminder_data[event.unified_msg_origin].append(d)
            self.scheduler.add_job(
                self._reminder_callback,
                "cron",
                id=d["id"],
                misfire_grace_time=60,
                **self._parse_cron_expr(cron_expression),
                args=[event.unified_msg_origin, d],
            )
            if human_readable_cron:
                reminder_time = f"{human_readable_cron}(Cron: {cron_expression})"
        else:
            d = {"text": text, "datetime": datetime_str, "id": str(uuid.uuid4())}
            self.reminder_data[event.unified_msg_origin].append(d)
            datetime_scheduled = datetime.datetime.strptime(
                datetime_str, "%Y-%m-%d %H:%M"
            )
            self.scheduler.add_job(
                self._reminder_callback,
                "date",
                id=d["id"],
                args=[event.unified_msg_origin, d],
                run_date=datetime_scheduled,
                misfire_grace_time=60,
            )
            reminder_time = datetime_str
        await self._save_data()
        yield event.plain_result(
            "成功设置待办事项。\n内容: "
            + text
            + "\n时间: "
            + reminder_time
            + "\n\n使用 /reminder ls 查看所有待办事项。\n使用 /tool off reminder 关闭此功能。"
        )

    @filter.command_group("reminder")
    def reminder(self):
        """The command group of the reminder."""
        pass

    async def get_upcoming_reminders(self, unified_msg_origin: str):
        """Get upcoming reminders."""
        reminders = self.reminder_data.get(unified_msg_origin, [])
        if not reminders:
            return []
        now = datetime.datetime.now()
        upcoming_reminders = [
            reminder
            for reminder in reminders
            if "datetime" not in reminder
            or datetime.datetime.strptime(reminder["datetime"], "%Y-%m-%d %H:%M") >= now
        ]
        return upcoming_reminders

    @reminder.command("ls")
    async def reminder_ls(self, event: AstrMessageEvent):
        """List upcoming reminders."""
        reminders = await self.get_upcoming_reminders(event.unified_msg_origin)
        if not reminders:
            yield event.plain_result("没有正在进行的待办事项。")
        else:
            reminder_str = "正在进行的待办事项：\n"
            for i, reminder in enumerate(reminders):
                time_ = reminder.get("datetime", "")
                if not time_:
                    cron_expr = reminder.get("cron", "")
                    time_ = reminder.get("cron_h", "") + f"(Cron: {cron_expr})"
                reminder_str += f"{i + 1}. {reminder['text']} - {time_}\n"
            reminder_str += "\n使用 /reminder rm <id> 删除待办事项。\n"
            yield event.plain_result(reminder_str)

    @reminder.command("rm")
    async def reminder_rm(self, event: AstrMessageEvent, index: int):
        """Remove a reminder by index."""
        reminders = await self.get_upcoming_reminders(event.unified_msg_origin)

        if not reminders:
            yield event.plain_result("没有待办事项。")
        elif index < 1 or index > len(reminders):
            yield event.plain_result("索引越界。")
        else:
            reminder = reminders.pop(index - 1)
            job_id = reminder.get("id")

            # self.reminder_data[event.unified_msg_origin] = reminder
            users_reminders = self.reminder_data.get(event.unified_msg_origin, [])
            for i, r in enumerate(users_reminders):
                if r.get("id") == job_id:
                    users_reminders.pop(i)

            try:
                self.scheduler.remove_job(job_id)
            except Exception as e:
                logger.error(f"Remove job error: {e}")
                yield event.plain_result(
                    f"成功移除对应的待办事项。删除定时任务失败: {str(e)} 可能需要重启 AstrBot 以取消该提醒任务。"
                )
            await self._save_data()
            yield event.plain_result("成功删除待办事项：\n" + reminder["text"])

    async def _reminder_callback(self, unified_msg_origin: str, d: dict):
        """The callback function of the reminder."""
        logger.info(f"Reminder Activated: {d['text']}, created by {unified_msg_origin}")
        await self.context.send_message(
            unified_msg_origin,
            MessageEventResult().message(
                "待办提醒: \n\n"
                + d["text"]
                + "\n时间: "
                + d.get("datetime", "")
                + d.get("cron_h", "")
            ),
        )

    async def terminate(self):
        self.scheduler.shutdown()
        await self._save_data()
        logger.info("Reminder plugin terminated.")
